﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using sharp_net.Domain;
using sharp_net.Repositories;
using sharp_net.Specifications;

namespace zkdao.Repositories.EF {

    public class EntityFrameworkRepository<T> : Repository<T> where T : class, IAggregateRoot {
        private readonly EFRepositoryContext efContext;

        public EntityFrameworkRepository(IRepositoryContext context): base(context) {
            if (context is EFRepositoryContext)
                this.efContext = context as EFRepositoryContext;
        }

        private MemberExpression GetMemberInfo(LambdaExpression lambda) {
            if (lambda == null)
                throw new ArgumentNullException("method");

            MemberExpression memberExpr = null;

            if (lambda.Body.NodeType == ExpressionType.Convert) {
                memberExpr =
                    ((UnaryExpression)lambda.Body).Operand as MemberExpression;
            } else if (lambda.Body.NodeType == ExpressionType.MemberAccess) {
                memberExpr = lambda.Body as MemberExpression;
            }

            if (memberExpr == null)
                throw new ArgumentException("method");

            return memberExpr;
        }
        private string GetEagerLoadingPath(Expression<Func<T, dynamic>> eagerLoadingProperty) {
            MemberExpression memberExpression = this.GetMemberInfo(eagerLoadingProperty);
            var parameterName = eagerLoadingProperty.Parameters.First().Name;
            var memberExpressionStr = memberExpression.ToString();
            var path = memberExpressionStr.Replace(parameterName + ".", "");
            return path;
        }

        protected override void DoAdd(T aggregateRoot) {
            efContext.RegisterNew<T>(aggregateRoot);
        }
        protected override bool DoExists(ISpecification<T> specification) {
            var count = efContext.Context.Set<T>().Count(specification.IsSatisfiedBy);
            return count != 0;
        }
        protected override void DoRemove(T aggregateRoot) {
            efContext.RegisterDeleted<T>(aggregateRoot);
        }
        protected override void DoUpdate(T aggregateRoot) {
            efContext.RegisterModified<T>(aggregateRoot);
        }
        protected override T DoGetByKey(params object[] keys) {
            return efContext.Context.Set<T>().Find(keys);
        }

        protected override T DoFind(ISpecification<T> specification) {
            return efContext.Context.Set<T>().Where(specification.IsSatisfiedBy).FirstOrDefault();
        }
        protected override IEnumerable<T> DoFindAll(ISpecification<T> specification, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder) {
            var query = efContext.Context.Set<T>().Where(specification.IsSatisfiedBy);
            if (sortPredicate != null) {
                switch (sortOrder) {
                    case SortOrder.Ascending:
                        return query.OrderBy(sortPredicate.Compile()).ToList();
                    case SortOrder.Descending:
                        return query.OrderByDescending(sortPredicate.Compile()).ToList();
                    default:
                        break;
                }
            }
            return query.ToList();
        }
        protected override IEnumerable<T> DoFindAll(ISpecification<T> specification, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int pageNumber, int pageSize) {
            if (pageNumber <= 0)
                throw new ArgumentOutOfRangeException("pageNumber", pageNumber, "The pageNumber is one-based and should be larger than zero.");
            if (pageSize <= 0)
                throw new ArgumentOutOfRangeException("pageSize", pageSize, "The pageSize is one-based and should be larger than zero.");

            var query = efContext.Context.Set<T>().Where(specification.IsSatisfiedBy);
            int skip = (pageNumber - 1) * pageSize;
            if (sortPredicate != null) {
                switch (sortOrder) {
                    case SortOrder.Ascending:
                        return query.OrderBy(sortPredicate.Compile()).Skip(skip).Take(pageSize).ToList();
                    case SortOrder.Descending:
                        return query.OrderByDescending(sortPredicate.Compile()).Skip(skip).Take(pageSize).ToList();
                    default:
                        break;
                }
            }
            return query.Skip(skip).Take(pageSize).ToList();
        }

        protected override T DoFind(ISpecification<T> specification, params Expression<Func<T, dynamic>>[] eagerLoadingProperties) {
            var dbset = efContext.Context.Set<T>();
            if (eagerLoadingProperties != null && eagerLoadingProperties.Length > 0) {
                var eagerLoadingProperty = eagerLoadingProperties[0];
                var eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                var dbquery = dbset.Include(eagerLoadingPath);
                for (int i = 1; i < eagerLoadingProperties.Length; i++) {
                    eagerLoadingProperty = eagerLoadingProperties[i];
                    eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                    dbquery = dbquery.Include(eagerLoadingPath);
                }
                return dbquery.Where(specification.IsSatisfiedBy).FirstOrDefault();
            } else
                return dbset.Where(specification.IsSatisfiedBy).FirstOrDefault();
        }
        protected override IEnumerable<T> DoFindAll(ISpecification<T> specification, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, params Expression<Func<T, dynamic>>[] eagerLoadingProperties) {
            var dbset = efContext.Context.Set<T>();
            IEnumerable<T> queryable = null;
            if (eagerLoadingProperties != null &&
                eagerLoadingProperties.Length > 0) {
                var eagerLoadingProperty = eagerLoadingProperties[0];
                var eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                var dbquery = dbset.Include(eagerLoadingPath);
                for (int i = 1; i < eagerLoadingProperties.Length; i++) {
                    eagerLoadingProperty = eagerLoadingProperties[i];
                    eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                    dbquery = dbquery.Include(eagerLoadingPath);
                }
                queryable = dbquery.Where(specification.IsSatisfiedBy);
            } else
                queryable = dbset.Where(specification.IsSatisfiedBy);

            if (sortPredicate != null) {
                switch (sortOrder) {
                    case SortOrder.Ascending:
                        return queryable.OrderBy(sortPredicate.Compile()).ToList();
                    case SortOrder.Descending:
                        return queryable.OrderByDescending(sortPredicate.Compile()).ToList();
                    default:
                        break;
                }
            }
            return queryable.ToList();
        }
        protected override IEnumerable<T> DoFindAll(ISpecification<T> specification, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int pageNumber, int pageSize, params Expression<Func<T, dynamic>>[] eagerLoadingProperties) {
            if (pageNumber <= 0)
                throw new ArgumentOutOfRangeException("pageNumber", pageNumber, "The pageNumber is one-based and should be larger than zero.");
            if (pageSize <= 0)
                throw new ArgumentOutOfRangeException("pageSize", pageSize, "The pageSize is one-based and should be larger than zero.");

            int skip = (pageNumber - 1) * pageSize;
            int take = pageSize;

            var dbset = efContext.Context.Set<T>();
            IEnumerable<T> queryable = null;
            if (eagerLoadingProperties != null &&
                eagerLoadingProperties.Length > 0) {
                var eagerLoadingProperty = eagerLoadingProperties[0];
                var eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                var dbquery = dbset.Include(eagerLoadingPath);
                for (int i = 1; i < eagerLoadingProperties.Length; i++) {
                    eagerLoadingProperty = eagerLoadingProperties[i];
                    eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                    dbquery = dbquery.Include(eagerLoadingPath);
                }
                queryable = dbquery.Where(specification.IsSatisfiedBy);
            } else
                queryable = dbset.Where(specification.IsSatisfiedBy);

            if (sortPredicate != null) {
                switch (sortOrder) {
                    case SortOrder.Ascending:
                        return queryable.OrderBy(sortPredicate.Compile()).Skip(skip).Take(take).ToList();
                    case SortOrder.Descending:
                        return queryable.OrderByDescending(sortPredicate.Compile()).Skip(skip).Take(take).ToList();
                    default:
                        break;
                }
            }
            return queryable.Skip(skip).Take(take).ToList();
        }
    }
}